package com.jinly.ryder.deploy.task;

import com.jinly.ryder.ansible.service.AnsibleService;
import com.jinly.ryder.ansible.service.model.AnsibleDeployResult;
import com.jinly.ryder.ansible.service.model.AnsibleParam;
import com.jinly.ryder.ansible.service.model.AnsibleTaskProgress;
import com.jinly.ryder.build.service.BuildArtifactService;
import com.jinly.ryder.common.service.TemplateService;
import com.jinly.ryder.common.utils.FileUtil;
import com.jinly.ryder.common.utils.SpringContextUtil;
import com.jinly.ryder.deploy.service.DeployConfigFileService;
import com.jinly.ryder.deploy.service.DeployRecordService;
import com.jinly.ryder.deploy.service.DeployService;
import com.jinly.ryder.deploy.service.model.DeployFileType;
import com.jinly.ryder.deploy.service.model.DeployProgress;
import com.jinly.ryder.deploy.service.model.DeployRecord;
import com.jinly.ryder.manage.service.model.AppInstance;
import com.jinly.ryder.manage.service.model.DeployGroup;
import com.jinly.ryder.manage.service.model.DeployStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class DeployTask implements Runnable {

    private final DeployTaskParam deployTaskParam;

    private final DeployService deployService;
    private final AnsibleService ansibleService;
    private final BuildArtifactService buildArtifactService;
    private final DeployRecordService deployRecordService;
    private final DeployConfigFileService deployConfigFileService;
    private final TemplateService templateService;

    public DeployTask(DeployTaskParam deployTaskParam) {
        this.deployTaskParam = deployTaskParam;
        deployService = (DeployService) SpringContextUtil.getBean("deployServiceImpl");
        ansibleService = (AnsibleService) SpringContextUtil.getBean("ansibleServiceImpl");
        buildArtifactService = (BuildArtifactService) SpringContextUtil.getBean("buildArtifactServiceImpl");
        deployRecordService = (DeployRecordService) SpringContextUtil.getBean("deployRecordServiceImpl");
        deployConfigFileService = (DeployConfigFileService) SpringContextUtil.getBean("deployConfigFileServiceImpl");
        templateService = (TemplateService) SpringContextUtil.getBean("templateServiceImpl");
    }

    @Override
    public void run() {
        deployTaskParam.getInstanceList().forEach(p -> deployService.updateInstanceDeployStatus(p.getId(), DeployStatus.DEPLOYING));
        try {
            boolean success = true;
            for (AppInstance instance : deployTaskParam.getInstanceList()) {
                if (success) {
                    success = deployInstance(instance);
                } else {
                    deployService.updateInstanceDeployStatus(instance.getId(), DeployStatus.WAITING);
                }
            }
        } catch (Exception e) {
            log.error("DeployTask err", e);
        }

    }

    private boolean deployInstance(AppInstance instance) {
        DeployRecord deployRecord = startRecord(deployTaskParam, instance);
        String tags = deployTaskParam.getDeployType().getCode().toLowerCase();
        String rootDir = deployTaskParam.getDeployRootDir() + "/" + deployRecord.getId();
        try {
            readyPkg(rootDir);
            log.info("[{}]pkg ready", deployRecord.getId());
            readyConf(rootDir);
            log.info("[{}]conf ready", deployRecord.getId());
            readyBin(instance, rootDir);
            log.info("[{}]bin ready", deployRecord.getId());

            String inventory = DeployFileType.INVENTORY.getFileName(deployTaskParam.getDeployRootDir(), instance.getHost());
            FileUtil.createFile(inventory, instance.getHost().getBytes(StandardCharsets.UTF_8));
            log.info("[{}]inventory ready", deployRecord.getId());
            Map<String, Object> paramMap = newAnsibleParam(instance, rootDir);
            AnsibleParam ansibleParam = new AnsibleParam("app_deploy.yaml", inventory, tags, paramMap);
            AnsibleDeployResult deployResult = ansibleService.exec(ansibleParam, (p) -> updateDeployProgress(instance, p));
            deployRecord.setResult(deployResult.isSuccess(), deployResult.getOutput());
        } catch (Exception e) {
            log.error("发布异常:{}", e.getMessage(), e);
            deployRecord.setResult(false, "发布异常:" + e.getMessage());
        } finally {
            try {
                deployService.updateInstanceDeployStatus(deployRecord);
                endRecord(deployRecord);
                deployRecordService.removeDeployProgress(instance.getHost(), instance.getPort());
            } catch (Exception e) {
                log.info("更新实例状态异常", e);
            }
            log.info("任务完成清理目录:{}", rootDir);
            FileUtil.delete(rootDir);
        }
        return deployRecord.getSuccess();
    }

    private void updateDeployProgress(AppInstance instance, AnsibleTaskProgress p) {
        DeployProgress deployProgress = new DeployProgress();
        BeanUtils.copyProperties(p, deployProgress);
        deployProgress.setHost(instance.getHost());
        deployProgress.setPort(instance.getPort());
        deployRecordService.updateDeployProgress(deployProgress);
    }

    private Map<String, Object> newAnsibleParam(AppInstance instance, String rootDir) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("deploy_root_dir", rootDir); // 部署工作目录
        paramMap.put("app_name", instance.getAppName()); //应用名称
        paramMap.put("artifact_name", deployTaskParam.getArtifact() != null ? deployTaskParam.getArtifact().getArtifactName() : null);//安装包
        paramMap.put("app_port", instance.getPort()); //应用端口
        return paramMap;
    }

    private void readyBin(AppInstance instance, String rootDir) {
        Map<String, String> shellParamMap = new HashMap<>();
        shellParamMap.put("java_jar_name", "/apps/" + instance.getAppName() + "/pkg/" + instance.getAppName() + ".jar");
        shellParamMap.put("java_opts", getJavaOpts(deployTaskParam.getDeployGroup(), instance));
        String shellContent = templateService.getLocalResult("ansible/file/start.sh", shellParamMap);
        String shellFileName = DeployFileType.SHELL.getFileName(rootDir, "start.sh");
        FileUtil.createFile(shellFileName, shellContent.getBytes(StandardCharsets.UTF_8));
    }

    private void readyConf(String rootDir) {
        deployConfigFileService.createConfigFile(rootDir, deployTaskParam.getDeployGroup().getId());
    }

    private void readyPkg(String rootDir) {
        FileUtil.createDir(rootDir + DeployFileType.PACKAGE.getDir());
        if (deployTaskParam.getArtifact() != null) {
            String targetFile = DeployFileType.PACKAGE.getFileName(rootDir, deployTaskParam.getArtifact().getArtifactName());
            log.info("准备部署包:{}", targetFile);
            buildArtifactService.moveArtifactFile(deployTaskParam.getArtifact().getId(), targetFile);
        }

    }

    private void endRecord(DeployRecord deployRecord) {
        deployRecordService.updateRecord(deployRecord);
    }

    private String getJavaOpts(DeployGroup deployGroup, AppInstance ins) {
        String javaOpts = "";
        if (deployGroup.getProps() != null && deployGroup.getProps().getJavaOpts() != null) {
            javaOpts = deployGroup.getProps().getJavaOpts();
        }
        if (!javaOpts.contains("spring.profiles.active")) {
            javaOpts += " -Dspring.profiles.active=" + deployGroup.getEnvType().getCode().toLowerCase();
        }
        if (!javaOpts.contains("logging.file.path")) {
            javaOpts += " -Dlogging.file.path=../logs/";
        }
        if (!javaOpts.contains("server.port")) {
            javaOpts += " -Dserver.port=" + ins.getPort();
        }
        return javaOpts;
    }


    private DeployRecord startRecord(DeployTaskParam param, AppInstance instance) {
        DeployRecord record = new DeployRecord();
        record.setDeployType(param.getDeployType());
        record.setHost(instance.getHost());
        record.setPort(instance.getPort());
        record.setApplyId(param.getFlowApplyId());
        record.setInstanceId(instance.getId());
        record.setBeginTime(LocalDateTime.now());
        record.setOperator(param.getOperator());
        deployRecordService.createRecord(record);
        return record;
    }
}
